package com.horizon.im.imServer.server.session.service;

import com.horizon.entity.ImNode;
import com.horizon.im.common.bean.Notification;
import com.horizon.im.imServer.distributed.ImWorker;
import com.horizon.im.imServer.distributed.OnlineCounter;
import com.horizon.im.imServer.distributed.WorkerRouter;
import com.horizon.im.imServer.server.session.LocalSession;
import com.horizon.im.imServer.server.session.RemoteSession;
import com.horizon.im.imServer.server.session.ServerSession;
import com.horizon.im.imServer.server.session.dao.SessionCacheDAO;
import com.horizon.im.imServer.server.session.dao.UserCacheDAO;
import com.horizon.im.imServer.server.session.entity.SessionCache;
import com.horizon.im.imServer.server.session.entity.UserCache;
import com.horizon.util.JsonUtil;
import io.netty.channel.ChannelHandlerContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Data
@Repository("SessionManager")
public class SessionManager {

    @Autowired
    UserCacheDAO userCacheDAO;

    @Autowired
    SessionCacheDAO sessionCacheDAO;


    private static SessionManager singleInstance = null;

    /*会话清单: 含本地会话、远程会话*/

    private ConcurrentHashMap<String, ServerSession> sessionMap = new ConcurrentHashMap();

    private static class singletonInstance {
        private static SessionManager sessionManager = new SessionManager();
    }

    public static SessionManager getSingleInstance() {
        return singletonInstance.sessionManager;
    }

    //登录成功之后增减session对象
    public void addLocalSession(LocalSession localSession) {
        //step1:保存本地session到会话清单
        String sessionId = localSession.getSessionId();
        sessionMap.put(sessionId, localSession);
        String uid = localSession.getUserId();

        //step2:缓存session到redis
        ImNode node = ImWorker.getInstance().getLocalNode();
        SessionCache sessionCache = new SessionCache(sessionId, uid, node);
        sessionCacheDAO.save(sessionCache);

        //step3:增加用户的session信息到用户缓存
        userCacheDAO.addSession(uid, sessionCache);

        //step4:增加用户数量
        log.info("本地session增加：{}, 在线总数:{}",
                JsonUtil.pojoToJson(localSession.getUserDTO()),
                OnlineCounter.getSingleInstance().getCurValue());

        //Step5:增加当前服务节点imworker的负载
        ImWorker.getInstance().incBalance();

        //step6:通知其他节点在线
        notifyOtherImNodeOnLine(localSession);
    }

    //通知其他节点会话上线
    private void notifyOtherImNodeOnLine(LocalSession session) {
        int type = Notification.SESSION_ON;
        Notification<Notification.ContentWrapper> notification = Notification.wrapContent(session.getSessionId());
        notification.setType(type);
        WorkerRouter.getInstance().sendNotification((JsonUtil.pojoToJson(notification)));
    }

    //根据用户id获取session会话
    public List<ServerSession> getSessionsBy(String userId) {
        //分布式：分布式保存user和所有session,根据sessionId删除用户会话
        UserCache userCache = userCacheDAO.get(userId);

        if (null == userCache) {
            log.info("用户：{}下线了？没有在缓冲中找到记录", userId);
            return null;
        }

        Map<String, SessionCache> allSession = userCache.getMap();
        if (null == allSession || allSession.size() == 0) {
            log.info("用户：{} 下线了? 没有任何会话 ", userId);
            return null;
        }
        List<ServerSession> sessions = new LinkedList<>();
        allSession.values().stream().forEach(sessionCache ->
        {
            String sid = sessionCache.getSessionId();
            //在本地，取得session
            ServerSession session = sessionMap.get(sid);
            //没有命中，创建远程的session，加入会话集合
            if (session == null) {
                session = new RemoteSession(sessionCache);
                sessionMap.put(sid, session);
            }
            sessions.add(session);
        });
        return sessions;
    }

    //删除session，session在这里设计了两层缓存；
    //1.以user为维度,保存了redis缓存：UserCache(userId,Map<SessionId, session>)
    //  以session为维度，保存了redis缓存：SessionCache(userId, sessionId, imNode)
    //2.以session为维度，保存了进程级缓存：Map<sessionId, session>
    //所以在增删改查中，需要考虑这两个维度的session都要保存值，具体的来说：
    //获取：当在redis中获取到session时，需要保存一份到进程级缓存中，再返回
    //删除：删除必须把redis缓存删除，再把进程级缓存也删除
    public void removeSession(String sessionId) {
        if (!sessionMap.containsKey(sessionId)) return;
        ServerSession serverSession = sessionMap.get(sessionId);
        String uid = serverSession.getUserId();
        //step1:减少用户数
        OnlineCounter.getSingleInstance().decrement();
        log.info("本地session减少:{}下线了，在线总数:{}", uid, OnlineCounter.getSingleInstance().getCurValue());

        ImWorker.getInstance().decrBalance();

        //step2:删除redis中的session
        sessionCacheDAO.remove(sessionId);
        //分布式：分布式保存user和所有session，根据sessionId 删除用户的会话
        userCacheDAO.removeSession(uid, sessionId);

        //step3:本地：从会话map中删除
        sessionMap.remove(sessionId);
    }

    //关闭连接
    public void closeSession(ChannelHandlerContext ctx)
    {

        LocalSession session =
                ctx.channel().attr(LocalSession.SESSION_KEY).get();

        if (null == session || !session.isValid())
        {
            log.error("session is null or isValid");
            return;
        }

        session.close();
        //删除本地的会话和远程会话
        this.removeSession(session.getSessionId());

        /**
         * 通知其他节点 ，用户下线
         */
        notifyOtherImNodeOffLine(session);

    }

    /**
     * 通知其他节点  有会话下线
     *
     * @param session session
     */
    private void notifyOtherImNodeOffLine(LocalSession session)
    {

        if (null == session || session.isValid())
        {
            log.error("session is null or isValid");
            return;
        }


        int type = Notification.SESSION_OFF;

        Notification<Notification.ContentWrapper> notification = Notification.wrapContent(session.getSessionId());
        notification.setType(type);
        WorkerRouter.getInstance().sendNotification(JsonUtil.pojoToJson(notification));
    }

    /**
     * 远程用户下线，数据堆积，删除session
     */
    public void removeRemoteSession(String sessionId)
    {
        if (!sessionMap.containsKey(sessionId))
        {
            return;
        }
        sessionMap.remove(sessionId);
    }
}
